// Linux 内核 0.12 版仅支持 a.out(Assembl y out) 执行文件和目标文件的格式 。 虽然这种格式目前已经渐
// 渐不用，而使用功能更为齐全的 ELF Executable and Link Format 格式，但是由于其简单性，作为学习
// 入门的材料正好比较适用。下面全面介绍一下 a.out 格式。
// 在头文件 a.out.h 中声明了三个数据结构以及一些宏。这些数据结构描述了系统上目标文件的结构。
// 在 Linux 0.12 系统中，编译产生的目标模块文件（简称模块文件）和链接生成的二进制可执行文件均采
// 用 a.out 格式。这里统称为目标文件。一个目标文件 共可有 七 部分（节）组成。它们依次为：
// a) 执行头部分 exec header ）。执行文件头部分。该部分中含有一些参数 exec 结构），内核使用
// 这些参数把执行文件加载到内存中并执行，而链接程序 ( 使用这些参数将一些模块文件组合成
// 一个可执行文件。这是目标文件唯一必要的组成部分。
// b) 代码段部分 text segment ）。含有程序执行时被加载到内存中的指令代码和相关数据。可以以
// 只读形式被加载。
// c) 数据段部分 data segment ）。这部分含有已经初始化过的数据，总是被加载到可读写的内存中。
// d) 代码重定位部分 text relocations ）。这部分含有供链接程序使用的记录数据。在组合目标模块
// 文件时用于定位代码段中的指针或地址。
// e) 数据重定位部分 data relocations ）。类似于代码重定位部分的作用，但是用于数据段中指针的
// 重定位。
// f) 符号表部分 s y mbol table ）。这部分同样含有供链接程序使用的记录数据，用于在二进制目标
// 模块文件之间对命名的变量和函数（符号）进行交叉引用。
// g) 字符串表部分 string table ）。该部分含有与符号名相对应的字符串。

#ifndef _A_OUT_H
#define _A_OUT_H

#define __GNU_EXEC_MACROS__

// 每个目标文件均以一个执行数据结构（ exec structure 开始。该数据结构的形式如下：
// 其中 各个字段的功能如下：
// - a_m agic 该字段含有三个子字段，分别是标志字段、机器类型标识字段和魔数字段。
// 不过对于 Linux 0.12 系统其目标文件只使用了其中的魔数子字段，并使用宏
// N_MAGIC() 来访问，它唯一地确定了二进制执行文件与其他加载的文件之间的区别。该子字段
// 中必须包含以下值之一：
// - OMAGIC 表示代码和数据段紧随在执行头后面并且是连续存放的。内核将代码和数据
// 段都加载到可读写内存中。编译器编译出的目标文件的魔数是 OMAGIC （八进制 0407 。
// - NMAGIC 同 OMAGIC 一样，代码和数据段紧随在执行头后面并且是连续存放的。然
// 而内核将代码加载到了只读内存中，并把数据段加载到代码段后下一页可读写内存边界开始。
// - ZMAGIC 内核在必要时从二进制执行文件中加载独立的页面。执行头部、代码段和数据段
// 都被链接程序处理成多个页面大小的块。内核加载的代码页面是只读的，而数据段的页面
// 是可写的。链接生成的可执行文件的魔数即是 ZMAGIC 0413 ，即 0x10b 。
// - a_text 该字段含有代码段的长度值，字节数。
// - a_data 该字段含有数据段的长度值，字节数。
// - a_bss 含有‘ bss 段’的长度，内核用其设置在数据段后初始的 break brk 。内核在加
// 载程序时，这段可写内存显现出处于数据段后面，并且初始时为全零。
// - a_ syms 含有符号表部分的字节长度值。
// - a_entry 含有内核将执行文件加载到内存中以后，程序执行起始点的内存地址。
// - a_trsize 该字段含有代码重定位表的大小，是字节数。
// - a_drsize 该字段含有数据重定位表的大小，是字节数。
struct exec {
  unsigned long a_magic;	            // 执行文件魔数。使用N_MAGIC等宏访问
  unsigned a_text;		               // 代码长度，字节数
  unsigned a_data;		               // 数据长度，字节数
  unsigned a_bss;		                  // 文件中的未初始化数据区长度，字节数
  unsigned a_syms;		               // 文件中的符号表长度，字节数
  unsigned a_entry;		               // 执行开始地址
  unsigned a_trsize;		               // 代码重定位信息长度，字节数
  unsigned a_drsize;		               // 数据重定位信息长度，字节数
};

// 宏定义，用于取上述exec结构中的魔数
#ifndef N_MAGIC
#define N_MAGIC(exec) ((exec).a_magic)
#endif

#ifndef OMAGIC
/* Code indicating object file or impure executable.  */
/* 指明是目标文件或者不纯的可执行文件的代号 */
// 历史上最早在PDP-11计算机上，魔数（幻数）是八进制数0407（0x107）。它位于执行程序
// 头结构的开始处。它原本是PDP-11的一条跳转指令，表示跳转到随后7个字后的代码开始处。
// 这样加载程序（loader）就可以在把执行文件放入内存后直接跳转到指令开始处运行。 现在
// 已没有程序使用这种方法，但这个八进制数却作为识别文件类型的标志（魔数）保留了下来。
// OMAGIC可以认为是Old Magic 的意思。
#define OMAGIC 0407                    // 0407 == 0x107

/* Code indicating pure executable.  */
/* 指明为纯可执行文件的代号 */           // New Magic，1975年以后开始使用，涉及虚存机制。
#define NMAGIC 0410                    // 0410 == 0x108

/* Code indicating demand-paged executable.  */
/* 指明为需求分页处理的可执行文件 */      // 其头结构占用文件开始处1K空间。
#define ZMAGIC 0413                    // 0413 == 0x10b
#endif /* not OMAGIC */

// 另外还有一个QMAGIC，是为了节约磁盘容量，把盘上执行文件的头结构与代码紧凑存放。


// 下面宏用于判断魔数字段的正确性。如果魔数不能被识别，则返回真。
#ifndef N_BADMAG
#define N_BADMAG(x)					\
 (N_MAGIC(x) != OMAGIC && N_MAGIC(x) != NMAGIC		\
  && N_MAGIC(x) != ZMAGIC)
#endif

#define _N_BADMAG(x)					\
 (N_MAGIC(x) != OMAGIC && N_MAGIC(x) != NMAGIC		\
  && N_MAGIC(x) != ZMAGIC)

// 宏定义，给出目标文件头结构末端到1024字节之间的长度
#define _N_HDROFF(x) (SEGMENT_SIZE - sizeof (struct exec))

// 下面宏用于操作目标文件的内容，包括.o模块文件和可执行文件

// 代码部分起始偏移值。
// 如果文件是 ZMAGIC类型的，即是执行文件，那么代码部分是从执行文件的1024字节偏移处
// 开始；否则执行代码部分紧随执行头结构末端（32字节）开始，即文件是模块文件（OMAGIC
// 类型）。
#ifndef N_TXTOFF
#define N_TXTOFF(x) \
 (N_MAGIC(x) == ZMAGIC ? _N_HDROFF((x)) + sizeof (struct exec) : sizeof (struct exec))
#endif

// 数据部分起始偏移值。从代码部分末端开始
#ifndef N_DATOFF
#define N_DATOFF(x) (N_TXTOFF(x) + (x).a_text)
#endif

// 代码重定位信息偏移值。从数据部分末端开始
#ifndef N_TRELOFF
#define N_TRELOFF(x) (N_DATOFF(x) + (x).a_data)
#endif

// 代码重定位信息偏移值。从数据部分末端开始
#ifndef N_DRELOFF
#define N_DRELOFF(x) (N_TRELOFF(x) + (x).a_trsize)
#endif

// 符号表偏移值。从上面数据段重定位表末端开始
#ifndef N_SYMOFF
#define N_SYMOFF(x) (N_DRELOFF(x) + (x).a_drsize)
#endif

// 字符串信息偏移值。在符号表之后
#ifndef N_STROFF
#define N_STROFF(x) (N_SYMOFF(x) + (x).a_syms)
#endif

// 下面对可执行文件被加载到内存（逻辑空间）中的位置情况进行操作。

/* Address of text segment in memory after it is loaded.  */
/* 代码段加载后在内存中的地址 */
#ifndef N_TXTADDR
#define N_TXTADDR(x) 0                 // 可见，代码段从地址0开始执行
#endif

/* Address of data segment in memory after it is loaded.
   Note that it is up to you to define SEGMENT_SIZE
   on machines not listed here.  */
/* 数据段加载后在内存中的地址。
   注意，对于下面没有列出名称的机器，需要你自己来定义
   对应的SEGMENT_SIZE */
#if defined(vax) || defined(hp300) || defined(pyr)
#define SEGMENT_SIZE PAGE_SIZE
#endif
#ifdef	hp300
#define	PAGE_SIZE	4096
#endif
#ifdef	sony
#define	SEGMENT_SIZE	0x2000
#endif	/* Sony.  */
#ifdef is68k
#define SEGMENT_SIZE 0x20000
#endif
#if defined(m68k) && defined(PORTAR)
#define PAGE_SIZE 0x400
#define SEGMENT_SIZE PAGE_SIZE
#endif

// 这里，Linux 0.12内核把内存页定义为4KB，段大小定义为1KB。因此没有使用上面的定义
#define PAGE_SIZE 4096
#define SEGMENT_SIZE 1024

// 以段为界的大小（进位方式）
#define _N_SEGMENT_ROUND(x) (((x) + SEGMENT_SIZE - 1) & ~(SEGMENT_SIZE - 1))

// 代码段尾地址
#define _N_TXTENDADDR(x) (N_TXTADDR(x)+(x).a_text)

#ifndef N_DATADDR
#define N_DATADDR(x) \
    (N_MAGIC(x)==OMAGIC? (_N_TXTENDADDR(x)) \
     : (_N_SEGMENT_ROUND (_N_TXTENDADDR(x))))
#endif

/* Address of bss segment in memory after it is loaded.  */
// 数据段开始地址。
// 如果文件是OMAGIC类型的，那么数据段就直接紧随代码段后面。否则的话数据段地址从代码
// 段后面段边界开始（1KB边界对齐）。例如ZMAGIC类型的文件。
#ifndef N_BSSADDR
#define N_BSSADDR(x) (N_DATADDR(x) + (x).a_data)
#endif

// a.out目标文件中符号表项结构（符号表记录结构）
// 其中各字段的含义为：
// - n_un.n_strx 含有本符号的名称在字符串表中的字节偏移值。当程序使用 nlist() 函数访问一个
// 符号表时，该字段被替换为 n_un.n_name 字段，这是内存中字符串的指针。
// - n_type 用于链接程序确定如何更新符号的值。使用位屏蔽 bitmasks 码
// 可以将 8 比特宽度的 n_type 字段分割成三个子字段。对于 N_EXT 类型位置
// 位的符号，链接程序将它们看作是“外部的”符号，并且允许其他二进制目标文件对它们的引
// 用。N_TYPE 屏蔽码用于链接程序感兴趣的比特位：
// - N_UNDF 一个未定义的符号。链接程序必须在其他二进制目标文件中定位一个具有相同名
// 称的外部符号，以确定该符号的绝对数据值。特殊情况下，如果 n_type 字段是非零 值，并
// 且没有二进制文件定义了这个符号，则链接程序在 BSS 段中将该符号解析为一个地址，保
// 留长度等于 n_value 的字节。如果符号在多于一个二进制目标文件中都没有定义并且这些
// 二进制目标文件对其长度值不一致，则链接程序将选择所有二进制目标文件中最大的长度。
// - N_ABS 一个绝对符号。链接程序不会更新一个绝对符号。
// - N_TEXT 一个代码符号。该符号的值是代码地址，链接程序在合并二进制目标文件时会更
// 新其值。
// - N_DATA 一个数据符号；与 N_TEXT 类似，但是用于数据地址。对应代码和数据符号的值
// 不是文件的偏移值而是 地址；为了找出文件的偏移，就有必要确定相关部分开始加载的地
// 址并减去它，然后加上该部分的偏移。
// - N_BSS 一个 BSS 符号；与代码或数据符号类似，但在二进制目标文件中没有对应的偏移。
// - N_FN 一个文件名符号。在合并二进制目标文件时，链接程序会将该符号插入在二进制
// 文件中的符号之前。符号的名称就是给予链接程序的文件名，而其值是二进制文件中首个
// 代码段地址。链接和加载时不需要文件名符号，但对于调 试 程序非常有用。
// - N_STAB 屏蔽码用于选择符号调 试 程序 例如 gdb) 感兴趣的位；其值在 stab() 中说明。
// - n_other 该字段按照 n_type 确定的段，提供有关符号重定位操作的符号独立性信息。目前，
// n_other 字段的最低 4 位含有两个值之一： AUX_FUNC 和 AUX_OBJECT 。 AUX_FUNC 将符号
// 与可调用的函数相关， AUX_OBJECT 将符号与数据相关，而不管它们是位于代码段还是数据
// 段。该字段主要用于链接程序 ld ，用于动态可执行程序的创建。
// - n_desc 保留给调 试 程序使用；链接程序不对其进行处理。不同的调试程序将该字段用作
// 不同的用途。
// - n_value 含有符号的值。对于代码、数据和 BSS 符号，这 是一个地址；对于其他符号（例
// 如调 试 程序符号），值可以是任意的。
#ifndef N_NLIST_DECLARED
struct nlist {
  union {
    char *n_name;
    struct nlist *n_next;
    long n_strx;
  } n_un;
  unsigned char n_type;                // 该字节分成3个字段
  char n_other;
  short n_desc;
  unsigned long n_value;
};
#endif

// 下面定义 nlist 结构中 n_type 字段值的常量符号
#ifndef N_UNDF
#define N_UNDF 0
#endif
#ifndef N_ABS
#define N_ABS 2
#endif
#ifndef N_TEXT
#define N_TEXT 4
#endif
#ifndef N_DATA
#define N_DATA 6
#endif
#ifndef N_BSS
#define N_BSS 8
#endif
#ifndef N_COMM
#define N_COMM 18
#endif
#ifndef N_FN
#define N_FN 15
#endif

// 以下3个常量定义是nlist结构中n_type字段的屏蔽码（八进制表示）
#ifndef N_EXT
#define N_EXT 1                        // 0x01（0b0000,0001）符号是否是外部的（全局的）
#endif
#ifndef N_TYPE
#define N_TYPE 036                     // 0x1e（0b0001,1110）符号的类型位
#endif
#ifndef N_STAB                         // STAB -- 符号表类型（Symbol table types）
#define N_STAB 0340                    // 0xe0（0b1110,0000）这几个比特用于符号调试器
#endif

/* The following type indicates the definition of a symbol as being
   an indirect reference to another symbol.  The other symbol
   appears as an undefined reference, immediately following this symbol.

   Indirection is asymmetrical.  The other symbol's value will be used
   to satisfy requests for the indirect symbol, but not vice versa.
   If the other symbol does not have a definition, libraries will
   be searched to find a definition.  */
/* 下面的类型指明一个符号的定义是作为对另一个符号的间接引用。紧接该
* 符号后面的其他的符号呈现为未定义的引用。
*
* 这种间接引用是不对称的。另一个符号的值将被用于满足间接符号的要求，
* 但反之则不然。如果另一个符号没有定义，则将搜索库来寻找一个定义 */
#define N_INDR 0xa

/* The following symbols refer to set elements.
   All the N_SET[ATDB] symbols with the same name form one set.
   Space is allocated for the set in the text section, and each set
   element's value is stored into one word of the space.
   The first word of the space is the length of the set (number of elements).

   The address of the set is made into an N_SETV symbol
   whose name is the same as the name of the set.
   This symbol acts like a N_DATA global symbol
   in that it can satisfy undefined external references.  */
/* 下面的符号与集合元素有关。所有具有相同名称N_SET[ATDB]的符号
   形成一个集合。在代码部分中已为集合分配了空间，并且每个集合元素
   的值存放在空间的一个字（word）中。空间的第一个字是集合的长度（元素数目）。
   集合的地址被放入一个N_SETV符号中，它的名称与集合同名。
   该符号的行为象一个N_DATA全局符号，因为它可以满足未定义的外部引用。*/

/* These appear as input to LD, in a .o file.  */
/* 以下这些符号在 .o 文件中是作为链接程序LD的输入。*/
#define	N_SETA	0x14		/* Absolute set element symbol */   /* 绝对集合元素符号 */
#define	N_SETT	0x16		/* Text set element symbol */       /* 代码集合元素符号 */
#define	N_SETD	0x18		/* Data set element symbol */       /* 数据集合元素符号 */
#define	N_SETB	0x1A		/* Bss set element symbol */        /* Bss集合元素符号 */

/* This is output from LD.  */
/* 下面是LD的输出。*/
#define N_SETV	0x1C		/* Pointer to set vector in data area.  */
                        /* 指向数据区中集合向量 */

#ifndef N_RELOCATION_INFO_DECLARED

/* This structure describes a single relocation to be performed.
   The text-relocation section of the file is a vector of these structures,
   all of which apply to the text section.
   Likewise, the data-relocation section applies to the data section.  */
/* 下面的结构描述了要执行的单个重定位操作。
   文件代码重定位部分是这些结构的一个数组，所有这些结构都适用于代码部分。
   同样，数据重定位部分用于数据部分。*/
// a.out目标文件中代码和数据重定位信息结构
// 该结构中各字段的含义如下：
// - r_address 该字段含有需要链接程序处理（编辑）的指针的字节偏移值。
// 代码重定位的偏移值是从代码段开始处计数的，数据重定位的偏移值是从数据段开始处计算的。
// 链接程序会将已经存储在该偏移处的值与使用重定位记录计算出的新值相加。
// - r_symbolnum 该字段含有符号表中一个符号结构的序号值（不是字节偏移值）。
// 链接程序在算出符号的绝对地址以后，就将该地址加到正在进行重定位的指针上。
// （如果r_extern比特位是0，那么情况就不同，见下面。）
// - r_pcrel 如果设置了该位，链接程序就认为正在更新一个指针，该指针使用 pc 相关寻址方
// 式，是属于机器码指令部分。当运行程序使用这个被重定位的指针时，该指针的地址被隐式地
// 加到该指针上。
// - r_length 该字段含有指针长度的 2 的次方值： 0 表示 1 字节长， 1 表示 2 字节长， 2 表示 4
// 字节长。
// - r_extern 如果被置位，表示该重定位 需要一个外部引用；此时链接程序必须使用一个符号
// 地址来更新相应指针。当该位是 0 时，则重定位是“局部”的；链接程序更新指针以反映各个
// 段加载地址中的变化，而不是反映一个符号值的变化。在这种情况下， r_symbolnum 字段的内
// 容是一个 n_type 值；这类字段告诉链接程序被重定位的指针指向那个段。
// - r_ pad Linux 系统中没有使用的 4 个比特位。在写一个目标文件时最好全置 0 。
struct relocation_info
{
  int r_address;                    /* 段内需要重定位的地址 */
  unsigned int r_symbolnum:24;      /* r_symbolnum的含义与r_extern有关。*/
  
  /* 非零表示值是一个pc相对偏移量，因而在其自己地址空间以及符号或指定的节部分改变时，它需要被重定位 */
  unsigned int r_pcrel:1;

  /* 需要被重定位的字段长度（是2的次方）。因此，若值是2则表示1<<2字节数。*/
  unsigned int r_length:2;
  
  /*  1 => 以符号的值重定位。
      r_symbolnum是文件符号表中符号的索引。
      0 => 以段的地址进行重定位。
      r_symbolnum是N_TEXT、N_DATA、N_BSS或N_ABS
      (N_EXT比特位也可以被设置，但是毫无意义)。*/
  unsigned int r_extern:1;
  
  /* 没有使用的4个比特位，但是当进行写一个目标文件时最好将它们清掉。*/
  unsigned int r_pad:4;
};
#endif /* no N_RELOCATION_INFO_DECLARED.  */


#endif /* __A_OUT_GNU_H__ */
